#!/usr/bin/python

import argparse
import json
import shutil
import subprocess
import sys
import os
import importlib
import importlib.util
import build_utils
import build_config

CONFIG_EXPORTS = []
CONFIG_ROOT_DIR = []
TOOLCHAIN_FILE = ""
CONFIG_VERSION = ""
CONFIG_CXXFLAGS = ""
CONFIG_CFLAGS = ""
CONFIG_LDFLAGS = ""
PACKAGE_MODULE_PATH=[]


def do_patch(patch_file):
    if os.path.exists(patch_file):
        pass
        # try:
        #     subprocess.check_call(['patch', '-p1', f'-iF {patch_file}'])
        #     build_utils.message_success('Patch Successful.')
        # except subprocess.CalledProcessError as e:
        #     build_utils.message_fail(f'Patch Failed: {e.stderr}')
        #     sys.exit(1)

def do_cmake(args, build_mode, topdir, bindir, install_dir):
    global CONFIG_EXPORTS
    global CONFIG_VERSION
    global TOOLCHAIN_FILE
    global CONFIG_CXXFLAGS
    global CONFIG_CFLAGS
    global CONFIG_LDFLAGS
    global PACKAGE_MODULE_PATH
    cmake_command = [
        'cmake',
        f'-S vendor/{args.product_name}',
        f'-B out/{args.product_name}',
        f'-DPRODUCT_VERSION={CONFIG_VERSION}',
        f'-DPRODUCT_NAME={args.product_name}',
        f'-DROOT_DIR={topdir}',
        f'-DPRODUCT_ROOT_DIR={topdir}',
        f'-DPRODUCT_BUILD_DIR={bindir}',
        f'-DPRODUCT_INSTALL_DIR={install_dir}',
        # f'-DCMAKE_BUILD_TYPE={build_mode}',
        f'-DCMAKE_INSTALL_PREFIX={install_dir}',
        f'-DJOBS_N={args.jobs}',
        f'-DCMAKE_C_FLAGS=-funwind-tables -ffunction-sections {CONFIG_CFLAGS}', #-rdynamic
        f'-DCMAKE_CXX_FLAGS=-funwind-tables -ffunction-sections {CONFIG_CXXFLAGS}', #-rdynamic
        f'-DCMAKE_EXE_LINKER_FLAGS={CONFIG_LDFLAGS}',
        f'-DPROTOC_PROGRAM={args.protoc}'
    ]

    print(f"cmake_command: {cmake_command}")
    if TOOLCHAIN_FILE != "":
        cmake_command.append(f'-DCMAKE_TOOLCHAIN_FILE={TOOLCHAIN_FILE}')
    module_paths = ""
    print("MODULE_EXPORTS:")
    for module_path in PACKAGE_MODULE_PATH:
        module_paths += module_path +";"
        print(f'{module_path}')
    if module_paths != "":
        cmake_command.append(f'-DCMAKE_MODULE_PATH={module_paths}')


    build_utils.appemd_mention_message(CONFIG_EXPORTS)

    print("CONFIG_EXPORTS:")
    for export in CONFIG_EXPORTS:
        cmake_command.append(f'-D{export}')
        print(f'{export}')
    # 定义cmake和make命令
    if not args.skip_cmake and os.path.exists(f'{topdir}/vendor/{args.product_name}/CMakeLists.txt'):
        # 删除Cache，否则影响结果
        if os.path.exists(f'{bindir}/CMakeCache.txt'):
            os.remove(f'{bindir}/CMakeCache.txt')
        # 执行cmake
        build_utils.exec_command(cmake_command, "cmake")

def do_make(args, topdir, bindir):
    make_command = ['make', f'--directory={topdir}/out/{args.product_name}',  f'--jobs={args.jobs}']

    # 执行make
    build_utils.exec_command(make_command, "build")

def do_install(args, topdir, install_dir):
    build_utils.print_trace_message('do_install', f'make install')
    # make install
    build_utils.exec_command(["make", "install", f'--directory={topdir}/out/{args.product_name}'], "make install")


def replace_env_vars(var):
    if type(var) == list:
        return [replace_env_vars(key) for key in var]
    elif type(var) == dict:
        return {key: replace_env_vars(value) for key, value in var.items()}
    elif type(var) != str:
        return var
    var = var.replace("$PRODUCT_ROOT_DIR", os.environ["PRODUCT_ROOT_DIR"])
    var = var.replace("$PRODUCT_VENDOR_DIR", os.environ["PRODUCT_VENDOR_DIR"])
    var = var.replace("$PRODUCT_INSTALL_DIR", os.environ["PRODUCT_INSTALL_DIR"])
    var = var.replace("$PRODUCT_BUILD_DIR", os.environ["PRODUCT_BUILD_DIR"])
    var = var.replace("$PRODUCT_NAME", os.environ["PRODUCT_NAME"])
    return var

def search_dep_for_module(dep, modules):
    for module in modules:
        if module["name"] == dep:
            return module
    return None

def do_build_deps(buildConfig, deps, modules, args):
    for dep in deps:
        build_utils.print_trace_message('do_build_deps', f'build deps : {dep}')
        dep_module = search_dep_for_module(dep, modules)
        if dep_module is None:
            build_utils.print_trace_message('do_build_config', f'dep {dep} not found module to build in vendor.json')
            exit(-1)
        do_build_module(buildConfig, dep_module, modules, args)

def do_build_module(buildConfig:build_config.BuildConfigParser, module, modules, args):
    global CONFIG_EXPORTS
    build_utils.print_trace_message('do_build_config', f'build module : {module}')
    if "deps" in module:
        deps = module["deps"]
        do_build_deps(buildConfig, deps, modules, args)

    if "exports" in module:
        CONFIG_EXPORTS.extend(module["exports"])
    if buildConfig.exist_submodule(module["name"]) and (module["name"] == args.build_target or args.build_target is None):
        buildConfig.build_submodule(module, CONFIG_EXPORTS)
    build_utils.print_trace_message('do_build_config', f'leave module : {module["name"]}')

def do_build_config(args, build_mode, topdir, bindir, installdir, vendor_config):
    if not os.path.exists(vendor_config):
        return
    config = {}
    with open(vendor_config, 'r') as file:
        config = json.load(file, object_hook=lambda obj: {
            key: replace_env_vars(value) for key, value in obj.items()
        })
    global CONFIG_VERSION
    global TOOLCHAIN_FILE
    global CONFIG_CXXFLAGS
    global CONFIG_CFLAGS
    global CONFIG_LDFLAGS
    global PACKAGE_MODULE_PATH

    print(config)
    CONFIG_VERSION = config["version"]
    TOOLCHAIN_FILE = config["toolchain"]
    CONFIG_CXXFLAGS = config["cxxflags"]
    CONFIG_CFLAGS = config["cflags"]
    CONFIG_LDFLAGS = config["ldflags"]
    modules = config["modules"]
    build_utils.print_trace_message('do_build_config', f'parse vendor.json, toolchain:{TOOLCHAIN_FILE}, version: {CONFIG_VERSION}')

    buildConfig = build_config.BuildConfigParser(build_mode, topdir, bindir, installdir, f'{topdir}/buildtools/build_config.json', replace_env_vars, args, TOOLCHAIN_FILE, CONFIG_CXXFLAGS, CONFIG_CFLAGS, CONFIG_LDFLAGS)
    for module in modules:
        do_build_module(buildConfig, module, modules, args)
    CONFIG_EXPORTS.extend(buildConfig.CONFIG_EXPORT_DIR)
    PACKAGE_MODULE_PATH = buildConfig.PACKAGE_MODULE_PATH

def main():
    # 创建解析器
    parser = argparse.ArgumentParser(description='Compile a project with cmake and make.')
    parser.add_argument('--product-name', required=True, help='The name of the product.')
    parser.add_argument('--device-type', required=False, help='The type of the device.')
    parser.add_argument('--build-target', required=False, help='The name of the build module.')
    parser.add_argument('--jobs', type=int, default=4, help='Number of jobs for make (default: 1).')
    parser.add_argument('--skip-cmake', action="store_true", help='Skip cmake.')
    parser.add_argument('--verbose', action="store_true", help='Output build debug info.')
    parser.add_argument('--force-build', action="store_true", help='Force to recompile.')
    parser.add_argument('--no-install', action="store_false", help='NO packages install')
    parser.add_argument('--build-mode', type=str, default="Release", required=False, help='Release or Debug.')
    parser.add_argument('--protoc', type=str, default="/usr/bin/protoc", required=False, help='protoc path.')

    # 解析命令行参数
    args = parser.parse_args()

    script_path = os.path.abspath(__file__)
    topdir = os.path.dirname(script_path)
    bindir = f'{topdir}/out/{args.product_name}'
    install_dir = f'{bindir}/install'
    build_mode = args.build_mode

    if args.build_target is None:
        build_target = 'All'
    else:
        build_target = args.build_target

    # 全局的环境变量
    os.environ['PRODUCT_ROOT_DIR'] = topdir
    os.environ["PRODUCT_NAME"] = args.product_name
    os.environ['PRODUCT_VENDOR_DIR'] = f'{topdir}/vendor/{args.product_name}'
    os.environ["PRODUCT_BUILD_DIR"] = bindir
    os.environ["PRODUCT_INSTALL_DIR"] = install_dir
    os.environ["PROTOC_PROGRAM"] = args.protoc

    if args.verbose:
        os.environ["VERBOSE"] = "1"

    # if os.path.exists(install_dir):
    #     shutil.rmtree(install_dir)
    do_patch(f'{topdir}/vendor/{args.product_name}/code.patch')
    print(f'topdir:{topdir}')
    do_build_config(args, build_mode, topdir, bindir, install_dir, f'{topdir}/vendor/{args.product_name}/vendor.json')

    if build_target != 'All':
        return
    do_cmake(args,build_mode, topdir, bindir, install_dir)
    do_make(args, topdir, bindir)
    if args.no_install:
        do_install(args, topdir, install_dir)

if __name__ == '__main__':
    main()